package com.lost.octopus.es.helper.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.lost.octopus.es.helper.constant.IndexSettingEnum;
import com.lost.octopus.es.helper.entity.model.FieldConfig;
import com.lost.octopus.es.helper.entity.model.QueryBuilder;
import com.lost.octopus.es.helper.entity.model.SortField;
import com.lost.octopus.es.helper.entity.query.PageIn;
import com.lost.octopus.es.helper.entity.query.PageOut;
import com.lost.octopus.es.helper.entity.query.QueryResult;
import com.lost.octopus.es.helper.entity.query.aggregation.AggregationStrategy;
import com.lost.octopus.es.helper.entity.query.aggregation.EsAggregations;
import com.lost.octopus.es.helper.entity.query.aggregation.NotAggregationStrategy;
import com.lost.octopus.es.helper.entity.query.highlight.HighlightDeal;
import com.lost.octopus.es.helper.entity.wrapper.EsQueryBlankWrapper;
import com.lost.octopus.es.helper.entity.wrapper.QueryWrapper;
import com.lost.octopus.es.helper.util.StringUtil;
import com.lost.octopus.es.helper.annotation.EsAggregation;
import com.lost.octopus.es.helper.annotation.EsSearch;
import com.lost.octopus.es.helper.annotation.EsSort;

import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetFieldMappingsRequest;
import org.elasticsearch.client.indices.GetFieldMappingsResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.PipelineAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;

/**
 * es查询基础实现
 * @author zhangbh
 **/
@Log4j2
public class SimpleEsServiceImpl implements EsService {

    @Resource
    private RestHighLevelClient client;
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    /**
     * 简单使用jackson转换对象
     */
    @Autowired(required = false)
    protected ObjectMapper objectMapper;

    /**
     * 高亮处理服务
     */
    @Autowired
    private HighlightDeal highlightDeal;

    /**
     * 请求行为对象
     */
    @Resource
    private RequestOptions defaultOption;

    /**
     * 内置查询包装类（用于 如sql中 and或者 or同层级的组合）
     */
    @Resource
    private final List<QueryWrapper> unionBuilders = new ArrayList<>();

    /**
     * 获取请求行为对象
     * @return 请求行为对象
     */
    private RequestOptions getOptions() {
        return defaultOption;
    }

    /**
     * 返回操作客户端
     * @return 高级rest操作类
     */
    protected RestHighLevelClient getClient() {
        return client;
    }

    public ObjectMapper getObjectMapper() {
        if (this.objectMapper == null) {
            this.objectMapper = JsonMapper.builder().build();
        }
        return this.objectMapper;
    }

    @SneakyThrows
    @Override
    public boolean deleteIndex(String index) {
        boolean exist = indexExist(index);
        if (!exist) {
            log.info("索引不存在，直接跳过");
            return false;
        }
        index = getRealIndexName(index);
        log.info("索引库存在，执行删除操作，真实index={}", index);
        AcknowledgedResponse delete = getClient().indices().delete(new DeleteIndexRequest(index), getOptions());
        return delete.isAcknowledged();
    }

    @Override
    public boolean reCreateIndex(String index, Class<?> clazz) {
        boolean exist = indexExist(index);
        if (!exist) {
            if (clazz == null) {
                log.info("索引不存在，且无类描述，无法创建索引！！！！！！！");
                return false;
            }
            // 初始化Mapping
            createWithMapping(clazz);
            return true;
        }
        deleteIndex(index);
        if (clazz == null) {
            log.info("索引不存在，且无类描述，无法创建索引！！！！！！！");
            return false;
        }
        // 初始化Mapping
        createWithMapping(clazz);
        return true;
    }

    @Override
    public <T> QueryResult<T> boolSearch(QueryBuilder search, Class<T> clazz, String... indices) throws IOException {
        QueryResult<Map<String, Object>> result = boolSearch(search, indices);
        if (CollectionUtils.isEmpty(result.getList())) {
            return QueryResult.of(result.getPage(), new ArrayList<>());
        }
        List<Map<String, Object>> list = result.getList();
        List<T> transList = new ArrayList<>();
        list.forEach(item -> transList.add(getObjectMapper().convertValue(item, clazz)));
        return QueryResult.of(result.getPage(), transList);
    }


    @Override
    public QueryResult<Map<String, Object>> boolSearch(QueryBuilder search, String... indices) throws IOException {
        // 查询，获取完整返回
        SearchResponse response = boolSearchGetResponse(search, indices);
        SearchHits hits = response.getHits();

        if (hits.getHits() == null || hits.getHits().length == 0) {
            return QueryResult.of(new PageOut(search.getPageIn(), hits.getTotalHits().value), new ArrayList<>());
        }

        // 查询后置操作
        List<Map<String, Object>> result = afterSearch(search, response);

        return QueryResult.of(new PageOut(search.getPageIn(), hits.getTotalHits().value), result);
    }

    @Override
    public SearchResponse boolSearchGetResponse(QueryBuilder search, String... indices) throws IOException {
        SearchRequest searchRequest = Requests.searchRequest(indices);
        // 自定义查询对象
        Object data = search.getData();

        // 查询条件，优先使用自定义条件，否则解析构建条件
        BoolQueryBuilder queryWrapper = useQuery(search, data, indices);

        PageIn pageIn = search.getPageIn();
        SearchSourceBuilder sourceBuilder = search.getSearchBuilder()
            // 加入查询条件
            .query(queryWrapper).from((pageIn.getCurrentPage() - 1) * pageIn.getLinesPerPage()).size(pageIn.getLinesPerPage());
        // 设置查询对象
        searchRequest.source(sourceBuilder);
        // 增加聚合条件
        addAggregation(sourceBuilder, data);
        // 增加排序
        addSortToBuilder(search, data, sourceBuilder);
        // 打印日志信息
        printQuery(searchRequest);
        return getClient().search(searchRequest, getOptions());
    }

    /**
     * 使用查询条件，可自定义
     * @param search 查询对象
     * @param data 查询实体
     * @param indices 索引
     * @return 查询对象
     */
    private BoolQueryBuilder useQuery(QueryBuilder search, Object data, String... indices) {
        BoolQueryBuilder queryWrapper = search.getQueryWrapper();
        if (queryWrapper == null) {
            queryWrapper = QueryBuilders.boolQuery();
            queryWrapper = addSearchParams(queryWrapper, data, indices);
        }
        return queryWrapper;
    }

    /**
     * 增加聚合条件
     * @param sourceBuilder 查询构建起
     * @param query 查询对象
     */
    protected void addAggregation(SearchSourceBuilder sourceBuilder, Object query) {
        // 先看有无自定义条件，有则增加
        EsAggregation annotation = query.getClass().getAnnotation(EsAggregation.class);
        if (annotation == null) {
            return;
        }
        EsAggregations esAggregations;
        try {
            // 默认不分组
            if (annotation.when() == NotAggregationStrategy.class) {
                return;
            }
            AggregationStrategy aggregationStrategy = annotation.when().newInstance();
            // 根据自定义的getter对象，获取聚合构建器
            esAggregations = aggregationStrategy.use(query);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("聚合配置获取失败", e);
        }
        // 获取的分组策略加入查询
        addAggregationToBuilder(sourceBuilder, esAggregations);
    }

    protected void addAggregationToBuilder(SearchSourceBuilder sourceBuilder, EsAggregations esAggregations) {
        if (esAggregations == null) {
            return;
        }
        List<AggregationBuilder> aggregationBuilders = esAggregations.getAggregations();
        List<PipelineAggregationBuilder> pipelineAggregations = esAggregations.getPipelineAggregations();

        // 增加到查询构建器中
        if (!CollectionUtils.isEmpty(aggregationBuilders)) {
            for (AggregationBuilder aggregation : aggregationBuilders) {
                log.info("增加的 AggregationBuilder={}", aggregation.getClass().getName());
                sourceBuilder.aggregation(aggregation);
            }
        }
        if (!CollectionUtils.isEmpty(pipelineAggregations)) {
            for (PipelineAggregationBuilder pipelineAggregation : pipelineAggregations) {
                log.info("增加的 PipelineAggregationBuilder={}", pipelineAggregation.getClass().getName());
                sourceBuilder.aggregation(pipelineAggregation);
            }
        }
    }

    @Override
    public boolean setMaxResultWindow(String index, Integer maxResult) {
        try {
            // 更新配置请求
            UpdateSettingsRequest settingsRequest = new UpdateSettingsRequest(index);
            settingsRequest.settings(Settings.builder().put(IndexSettingEnum.max_result_window.getKey(), String.valueOf(maxResult)).build());
            return getClient().indices().putSettings(settingsRequest, getOptions()).isAcknowledged();
        } catch (IOException e) {
            log.error("indexSetting error", e);
            return false;
        }
    }

    @Override
    public boolean indexSetting(String index, @NonNull Map<String, Object> settings) {
        try {
            // 判断是否存在
            boolean exist = indexExist(index);
            AcknowledgedResponse response;
            IndicesClient client = getClient().indices();
            if (exist) {
                response = client.putSettings(new UpdateSettingsRequest(index).settings(settings), getOptions());
            } else {
                response = client.create(new CreateIndexRequest(index).settings(settings), getOptions());
            }
            // 加入所有配置
            return response.isAcknowledged();
        } catch (IOException e) {
            log.error("indexSetting error", e);
            return false;
        }
    }

    /**
     * 创建索引且创建mapping
     * @param clazz 类型 需要注解 {@link Document}
     */
    protected void createWithMapping(Class<?> clazz) {
        elasticsearchRestTemplate.indexOps(clazz).createWithMapping();
    }


    @SneakyThrows
    @Override
    public final boolean indexExist(String index) {
        return getClient().indices().exists(new GetIndexRequest(index), getOptions());
    }

    @SneakyThrows
    @Override
    public final String getRealIndexName(@NonNull String aliasName) {
        GetAliasesResponse alias = null;
        // ES 查询时即使别名不存在，也会返回错误的json报文，虽然http status 不是200。
        // 但是思明存在返回报文被未知配置拦截，此时返回的时page not found, content-type:text/html,此处做兼容
        try {
            alias = getClient().indices().getAlias(new GetAliasesRequest(aliasName), getOptions());
        } catch (Exception e) {
            log.error("通过别名获取索引失败，es返回预料之外的出参结果。error = ", e);
            log.error("通过别名获取索引失败，es返回预料之外的出参结果。aliasName = {}， 直接返回当作原索引名", aliasName);
            return aliasName;
        }
        // 没有别名，就当成是真实索引id
        if (alias.status().equals(RestStatus.NOT_FOUND)) {
            log.info("没有别名，{} 直接当做原索引名", aliasName);
            return aliasName;
        } else {
            // 循环获取真实索引id，key为真实索引id，可以对应多个别名
            Map<String, Set<AliasMetadata>> aliases = alias.getAliases();
            AtomicReference<String> realIndexRef = new AtomicReference<>();
            aliases.forEach((key, aliasNames) -> {
                String realIndex = realIndexRef.get();
                if (StringUtils.hasText(realIndex)) {
                    return;
                }
                // 遍历，如果有某个别名符合，就返回key
                aliasNames.stream().filter(item -> Objects.equals(item.getAlias(), aliasName))
                    .findAny().ifPresent(aliasObj -> realIndexRef.set(key));
            });
            log.info("根据别名={} 获取到的实际索引名={}", aliasName, realIndexRef.get());
            return realIndexRef.get();
        }
    }

    /**
     * 在查询后，做后置操作
     * @param search 查询对象
     * @param response es查询返回对象
     * @return 返回处理好的数据
     */
    protected List<Map<String, Object>> afterSearch(QueryBuilder search, SearchResponse response) {
        // 是否使用高亮配置
        HighlightBuilder highlighter = search.getSearchBuilder().highlighter();
        boolean useHighlight = highlighter != null && !CollectionUtils.isEmpty(highlighter.fields());

        SearchHit[] hitsData = response.getHits().getHits();
        List<Map<String, Object>> result = new ArrayList<>();
        // 获取数据，设置高亮
        Arrays.stream(hitsData).forEach(hitsDatum -> {
            Map<String, Object> sourceAsMap = hitsDatum.getSourceAsMap();
            // 检查增加高亮字段
            if (useHighlight && !CollectionUtils.isEmpty(hitsDatum.getHighlightFields())) {
                highlightDeal.doTransHighlightFields(sourceAsMap, hitsDatum.getHighlightFields());
            }
            result.add(sourceAsMap);
        });
        return result;
    }

    /**
     * 校验index是否存在，否则抛出异常
     * @param index 索引id
     */
    protected final void indexExistOrThrow(String index) {
        boolean exist = indexExist(index);
        if (!exist) {
            throw new IllegalArgumentException("当前操作需要索引存在");
        }
    }


    /**
     * 获取解析对象注解后的查询对象,如果想自定义扩展可以使用此方法
     * @param query 查询对象
     * @param indices 相关的索引
     * @return 查询包装类
     */
    protected BoolQueryBuilder getBoolQueryBuilder(Object query, String... indices) {
        return addSearchParams(QueryBuilders.boolQuery(), query, indices);
    }

    /**
     * 获取排序条件，读取EsDocument注解，支持统一在类上
     * @param query 请求对象
     * @return 请求构造器
     */
    protected List<FieldSortBuilder> getSorts(Object query) {
        Field[] fields = getDeclaredFields(query.getClass()).toArray(new Field[0]);
        // 循环确定有sort的注解字段
        if (fields.length == 0) {
            return Collections.emptyList();
        }
        // 循环先根据order排序好
        List<SortField> sortList = new ArrayList<>();
        Arrays.stream(fields).forEach(field -> {
            EsSort sort = field.getAnnotation(EsSort.class);
            if (sort == null) {
                return;
            }
            boolean addSort = isAddSort(query, sort);
            if (!addSort) {
                return;
            }
            sortList.add(new SortField(field.getName(), sort, sort.order()));
        });
        return getFieldSort(sortList);
    }

    /**
     * 循环获取字段信息
     * @param clazz 对象类型
     * @return 字段信息
     */
    private List<Field> getDeclaredFields(Class<?> clazz) {
        if (Object.class.equals(clazz)) {
            return new ArrayList<>();
        }
        List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
        fields.addAll(getDeclaredFields(clazz.getSuperclass()));
        return fields;
    }

    /**
     * 是否加入排序
     * @param query 请求对象
     * @param sort 排序注解
     * @return 是否加入排序
     */
    protected boolean isAddSort(Object query, EsSort sort) {
        String checkFieldName = sort.sortOn();
        boolean addSort = true;
        // 检查是否需要排序,字段存在且非空
        String needSort = "1";
        if (StringUtils.hasText(checkFieldName)) {
            addSort = getDeclaredFields(query.getClass()).stream()
                .anyMatch(checkField -> {
                    ReflectionUtils.makeAccessible(checkField);
                    return checkField.getName().equals(checkFieldName)
                        // 字段是否是 "1"
                        && needSort.equals(ReflectionUtils.getField(checkField, query));
                });

        }
        return addSort;
    }

    /**
     * 获取字段排序Builder
     * @param sortList 排序字段对象
     * @return 返回数据
     */
    protected List<FieldSortBuilder> getFieldSort(List<SortField> sortList) {
        if (sortList.isEmpty()) {
            return Collections.emptyList();
        }
        // 根据order排序好后的数据
        List<SortField> sortedList = sortList.stream().sorted(Comparator.comparingInt(SortField::getOrder))
            .collect(Collectors.toList());
        List<FieldSortBuilder> result = new ArrayList<>();
        // 设置条件
        sortedList.forEach(sortField -> {
            // 注解对象
            EsSort sortInfo = sortField.getSortInfo();
            // 映射类型
            String name = sortInfo.unmappedType().name();
            // 自定义fieldName
            String fieldName = StringUtils.hasText(sortInfo.name()) ? sortInfo.name() : sortField.getFieldName();

            result.add(SortBuilders.fieldSort(fieldName).unmappedType(name)
                .order(SortOrder.valueOf(sortInfo.orderBy().name())));
        });
        return result;
    }

    /**
     * 增加查询条件
     * @param queryWrapper 请求包装对象
     * @param query 请求对象
     * @param indices 索引
     */
    protected BoolQueryBuilder addSearchParams(BoolQueryBuilder queryWrapper, Object query, String... indices) {
        Field[] fields = getDeclaredFields(query.getClass()).toArray(new Field[0]);
        // 循环确定有sort的注解字段
        if (fields.length == 0) {
            return queryWrapper;
        }
        // 获取查询参数
        List<FieldConfig> searchParam = getSearchParamByFields(query, fields);

        if (searchParam.isEmpty()) {
            return queryWrapper;
        }
        searchParam.forEach(searchField -> {
            Class<? extends QueryWrapper> wrapperClazz = searchField.getQueryType();
            Optional<QueryWrapper> hasOption = unionBuilders.stream()
                .filter(unionBuilder -> unionBuilder.getClass().equals(wrapperClazz)).findAny();
            if (hasOption.isPresent()) {
                QueryWrapper unionBuilder = hasOption.get();
                // 获取字段类型
                addFieldType(searchField, indices);

                unionBuilder.addWrapper(queryWrapper, searchField);
            } else {
                throw new IllegalArgumentException("没有匹配到对应的实现，请确认有注册该实现{}" + wrapperClazz.getName());
            }
        });
        return queryWrapper;
    }

    /**
     * 通过字段获取查询条件用对象
     * @param query 查询对象
     * @param fields 对象配置{@link EsSearch}字段
     * @return 查询分析列表
     */
    protected List<FieldConfig> getSearchParamByFields(Object query, Field[] fields) {
        List<FieldConfig> searchList = new ArrayList<>();
        Arrays.stream(fields).forEach(field -> {
            EsSearch esField = field.getAnnotation(EsSearch.class);
            if (esField == null) {
                return;
            }
            ReflectionUtils.makeAccessible(field);
            Object value = ReflectionUtils.getField(field, query);
            // 如果为空也不加入查询
            if (StringUtil.isBlank(value)) {
                return;
            }
            // 查询的字段名，如果有定制化，则优先定制化
            String fieldName = !StringUtils.hasText(esField.name()) ? field.getName() : esField.name();

            searchList.add(new FieldConfig(
                fieldName,
                value,
                null,// 这时候字段类型还没有
                esField.queryType(),
                esField.matchType(),
                esField.matchMode(),
                esField.searchMode(),
                esField.otherParam()
            ));
        });
        return searchList;
    }

    /**
     * 打印最终的查询信息，此打印比较重要，所以单独设置参数控制
     * @param request 请求对象
     */
    protected void printQuery(SearchRequest request) {
        String[] indices = request.indices();
        log.info("最终查询条件\nindices={}\n查询参数={}\nQuery对象={}\n", indices, request, request.source());
    }

    /**
     * 解析es内Mapping实际的字段类型，用于wrapper高效处理
     * 如字符串类型的wrapper {@link EsQueryBlankWrapper} 需要增加非空白字符串，其他的只要notExist即可
     * @param searchField 查询参数
     * @param indices 索引
     */
    protected void addFieldType(FieldConfig searchField, String... indices) {
        GetFieldMappingsRequest mappingsRequest = new GetFieldMappingsRequest();
        GetFieldMappingsResponse mapping;
        try {
            String fieldName = searchField.getName();
            // 获取所有的字段
            mapping = getClient().indices()
                .getFieldMapping(mappingsRequest.indices(indices).fields(fieldName), getOptions());

            for (String index : indices) {
                GetFieldMappingsResponse.FieldMappingMetadata metadata = mapping.fieldMappings(index, fieldName);
                if (metadata == null) {
                    continue;
                }
                // 获取字段配置
                Map<String, Object> fieldMap = metadata.sourceAsMap();
                Object typeMap = fieldMap.get(fieldName);
                if (typeMap instanceof Map) {
                    String type = (String) ((Map<?, ?>) typeMap).get("type");
                    searchField.setFieldType(type);
                }
            }
        } catch (IOException e) {
            throw new IllegalArgumentException("索引：" + Arrays.toString(indices) + "获取类型失败");
        }
    }

    /**
     * 增加排序，如有自定义方法则优先使用自定义
     * @param search 查询构造对象
     * @param data 查询对象
     * @param sourceBuilder 搜索对象
     */
    private void addSortToBuilder(QueryBuilder search, Object data, SearchSourceBuilder sourceBuilder) {
        List<FieldSortBuilder> sorts = search.getSorts();
        if (search.isNeedSort()) {
            // 如果没有自定义，则尝试读取类上配置的排序
            if (CollectionUtils.isEmpty(sorts)) {
                sorts = getSorts(data);
            }
            if (!CollectionUtils.isEmpty(sorts)) {
                sorts.forEach(sourceBuilder::sort);
            }
        }
    }

}
